use dirs::home_dir;
use serde::Deserialize;
use std::fs::{self, create_dir_all, metadata};
use std::io::{stdin};
use reqwest;
use std::time::Instant;

/// Represents the pip configuration structure read from the YAML file.
#[derive(Debug, Deserialize)]
struct PipConfig {
    active_source: String,
    sources: std::collections::HashMap<String, SourceConfig>,
}

/// Represents the configuration for a single pip source.
#[derive(Debug, Deserialize)]
struct SourceConfig {
    index_url: String,
    trusted_host: String,
}

/// Reads the pip source configuration from the YAML file.
///
/// # Errors
/// - Fails if the file cannot be read.
/// - Fails if the file content cannot be parsed as YAML.
///
/// # Returns
/// A `PipConfig` struct containing the parsed configuration.
fn read_config() -> PipConfig {
    let config_content = fs::read_to_string("pip_sources.yml").expect("Failed to read config file");
    serde_yaml::from_str(&config_content).expect("Failed to parse config file")
}

/// Retrieves the configuration of the active pip source.
///
/// # Arguments
/// - `config`: A reference to the `PipConfig` struct.
///
/// # Panics
/// Panics if the active source is not found in the configuration.
///
/// # Returns
/// A reference to the `SourceConfig` of the active source.
fn get_active_source(config: &PipConfig) -> &SourceConfig {
    config.sources.get(&config.active_source).expect("Active source not found")
}

/// Gets the path to the pip configuration file.
///
/// # Errors
/// Fails if the home directory cannot be retrieved.
///
/// # Returns
/// A `PathBuf` representing the path to the pip configuration file.
fn get_pip_config_file() -> std::path::PathBuf {
    let home_dir = home_dir().expect("Failed to get home directory");
    let pip_config_dir = home_dir.join(".pip");
    pip_config_dir.join("pip.conf")
}

/// Prints the existing pip source configuration if the file exists.
/// Displays the file path, size, and the content enclosed in a border.
///
/// # Arguments
/// - `pip_config_file`: A reference to the `PathBuf` of the pip configuration file.
///
/// # Errors
/// Fails if the existing pip configuration file cannot be read.
fn print_existing_config(pip_config_file: &std::path::PathBuf) {
    if pip_config_file.exists() {
        let metadata = metadata(pip_config_file).expect("Failed to get file metadata");
        let file_size = metadata.len();
        let existing_config = fs::read_to_string(pip_config_file).expect("Failed to read existing pip config file");

        println!("File Path: {:?}", pip_config_file);
        println!("File Size: {} bytes", file_size);

        let lines: Vec<&str> = existing_config.lines().collect();
        let max_line_length = lines.iter().map(|line| line.len()).max().unwrap_or(0);
        let border = "+".to_string() + &"-".repeat(max_line_length + 2) + "+";

        println!("{}", border);
        for line in lines {
            println!("| {}{} |", line, " ".repeat(max_line_length - line.len()));
        }
        println!("{}", border);
    } else {
        println!("No existing pip configuration file found.");
    }
}

/// Tests the response time of a given URL.
///
/// # Arguments
/// - `url`: The URL to test.
///
/// # Returns
/// The response time in milliseconds, or `None` if the request fails.
async fn test_response_time(url: &str) -> Option<u128> {
    let client = reqwest::Client::new();
    let start = Instant::now();
    match client.get(url).send().await {
        Ok(_) => Some(start.elapsed().as_millis()),
        Err(_) => None,
    }
}

/// Prompts the user to select a pip source.
///
/// # Arguments
/// - `config`: A reference to the `PipConfig` struct.
///
/// # Returns
/// A reference to the `SourceConfig` of the selected source. If the input is invalid or empty,
/// it returns the active source.
async fn prompt_user_for_source(config: &PipConfig) -> &SourceConfig {
    let source_names: Vec<&String> = config.sources.keys().collect();
    println!("Available pip sources:");
    for (i, name) in source_names.iter().enumerate() {
        // 将 &String 转换为 &str
        let source = config.sources.get(name.as_str()).unwrap();
        let response_time = test_response_time(&source.index_url).await;
        match response_time {
            Some(time) => println!("{}. {} ({}): {} ms", i + 1, name, source.index_url, time),
            None => println!("{}. {} ({}): Failed to connect", i + 1, name, source.index_url),
        }
    }
    println!("Enter the number of the source you want to use (default: {}): ", config.active_source);
    let mut input = String::new();
    stdin().read_line(&mut input).expect("Failed to read input");
    let input = input.trim();
    if input.is_empty() {
        return get_active_source(config);
    }
    if let Ok(index) = input.parse::<usize>() {
        if index > 0 && index <= source_names.len() {
            return config.sources.get(source_names[index - 1].as_str()).unwrap();
        }
    }
    println!("Invalid input. Using the default source: {}.", config.active_source);
    get_active_source(config)
}

/// Prompts the user to confirm the update of the pip source.
///
/// # Arguments
/// - `active_source`: A reference to the `SourceConfig` of the source to be updated.
///
/// # Returns
/// `true` if the user confirms the update, `false` otherwise.
fn confirm_update(active_source: &SourceConfig) -> bool {
    println!(
        "You are about to update the pip source to:\nIndex URL: {}\nTrusted Host: {}",
        active_source.index_url,
        active_source.trusted_host
    );
    println!("Do you want to proceed? [Y/N] (default: N)");
    let mut input = String::new();
    stdin().read_line(&mut input).expect("Failed to read input");
    let input = input.trim().to_lowercase();
    input == "y" || input == "yes"
}

/// Generates the content for the pip configuration file based on the selected source.
///
/// # Arguments
/// - `active_source`: A reference to the `SourceConfig` of the selected source.
///
/// # Returns
/// A string containing the content for the pip configuration file.
fn generate_config_content(active_source: &SourceConfig) -> String {
    format!(
        "[global]\nindex-url = {}\n[install]\ntrusted-host = {}",
        active_source.index_url,
        active_source.trusted_host
    )
}

/// Writes the generated configuration content to the pip configuration file.
///
/// # Arguments
/// - `pip_config_file`: A reference to the `PathBuf` of the pip configuration file.
/// - `config_content`: A reference to the string containing the configuration content.
///
/// # Errors
/// - Fails if the parent directory cannot be created.
/// - Fails if the configuration content cannot be written to the file.
fn write_config_file(pip_config_file: &std::path::PathBuf, config_content: &str) {
    let pip_config_dir = pip_config_file.parent().expect("Failed to get parent directory");
    if !pip_config_dir.exists() {
        create_dir_all(pip_config_dir).expect("Failed to create pip config directory");
    }
    fs::write(pip_config_file, config_content).expect("Failed to write pip config file");
}

/// The main entry point of the program.
///
/// Reads the configuration, checks and prints the existing pip source configuration,
/// prompts the user to select a source, confirms the update, generates the configuration
/// content, and writes it to the pip configuration file.
#[tokio::main]
async fn main() {
    let config = read_config();
    let pip_config_file = get_pip_config_file();
    print_existing_config(&pip_config_file);
    let active_source = prompt_user_for_source(&config).await;
    if !confirm_update(active_source) {
        println!("Aborting the update.");
        return;
    }
    let config_content = generate_config_content(active_source);
    write_config_file(&pip_config_file, &config_content);
    println!("Pip source has been replaced with the selected source. Config file: {:?}", pip_config_file);
}